import static org.junit.Assert.*;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;


/**
 * The Class VendingMachineTest.
 */
public class VendingMachineTest {

	//myVendingMachine Object
	/** VendingMachine - myVendingMachine Object*/
	public VendingMachine myVendingMachine;
	
	//sunChips Object
	/** VendingMachineItem Object - sunChips*/
	private VendingMachineItem sunChips;
	
	//donut Object
	/** VendingMachineItem Object - donut*/
	private VendingMachineItem donut;
	
	/** VendingMachineItem Object - coke*/
	private VendingMachineItem coke;
	/** VendingMachineItem pepsi - sunChips*/
	private VendingMachineItem pepsi;
	
	/** The item array. */
	private VendingMachineItem[] itemArray;
	
	/** The Constant NUM_SLOTS. */
	public static final int NUM_SLOTS = 4;
	
	/**
	 * Set up the Test Run by creating Objects.
	 * @throws Exception the exception
	 * 
	 */
	@Before
	public void setUp() throws Exception {
		myVendingMachine = new VendingMachine();
		sunChips = new VendingMachineItem("Sun Chips", 2.00);
		donut = new VendingMachineItem("Donut", 2.50);
		coke =  new VendingMachineItem("Coke", 0.75);
		pepsi = new VendingMachineItem("Pepsi", 1.00);
		
		itemArray = new VendingMachineItem[NUM_SLOTS];
		for (int i = 0; i < NUM_SLOTS; i++) {
				itemArray[i] = null;
		}
	}

	/**
	 * Tear down.
	 *
	 * @throws Exception the exception
	 */
	@After
	public void tearDown() throws Exception {
	}

	
	
	/**
	 * Test add item for Correct Slot.
	 * Postcondition: Test run will be true if the item's slot and the expected slot for the item are same.
	 */
	@Test public void testAddItem() {
		myVendingMachine.addItem(sunChips, VendingMachine.D_CODE);
		assertEquals(sunChips,myVendingMachine.getItem(VendingMachine.D_CODE));
	}

	
	/**
	 * Test add item for wrong item in the specified slot.
	 * Postcondition: Test run will be false if the item's slot and the expected slot for the item are different.
	 */
	@Test public void testAddItem_Wrong_Item() {
		myVendingMachine.addItem(donut, VendingMachine.C_CODE);
		assertEquals(sunChips,myVendingMachine.getItem(VendingMachine.C_CODE));
	}

	/**
	 * Test add item for already occupied slot.
	 * Postcondition: Test run will be true if the exception is correctly caught, when
	 * the item's slot is occupied by other item.
	 * @throws VendingMachineException If trying to add item in a non-empty slot.
	 */
    @Test
    public void testAddItem_FulledSlot() throws Exception{
	            myVendingMachine.addItem(coke,"A");
	            myVendingMachine.addItem(pepsi, "B");         
	            try{
	            	myVendingMachine.addItem(pepsi, "A");	
	            }catch(VendingMachineException e){
	            	String msg = "Slot A" + " already occupied";
	            	assertEquals(msg, e.getMessage());
	            }
	}
    
    

	/**
	 * Test add item for invalid code slot.
	 * Postcondition: Test run will be true if the exception is correctly caught when
	 * trying to add item to an invalid slot.
	 * @throws VendingMachineException If trying to add item in an invalid slot.
	 */
    @Test 
    public void testAddItem_InvalidCode() throws Exception{
	      
    		try{
    			myVendingMachine.addItem(coke,"X");
    	    }catch(VendingMachineException e){
    	    	String msg="Invalid code for vending machine item";
    	    	assertEquals(msg, e.getMessage());
    	    }
	}
    
    

	/**
	 * Test add item for already occupied slot for the message - Slot already occupied.
	 * Postcondition: Test run will be true if the exception is correctly caught when
	 * trying to add item to already occupied slot.
	 * @throws VendingMachineException If trying to add item in an invalid slot.
	 */
    @Test 
    public void testAddItem_CheckMessage() throws Exception{
    	myVendingMachine.addItem(coke,"D");
    	try{
	       myVendingMachine.addItem(pepsi,"D");
    	}
    	catch(VendingMachineException e){
    		String msg = "Slot D" + " already occupied";
			assertEquals(msg, e.getMessage());
    	}
	}
    
    
	/**
	 * Test remove item from a slot.
	 * Postcondition: Test run will be true if the item removed is present in the slot mentioned.
	 */
	@Test
	public void testRemoveItem(){
		myVendingMachine.addItem(donut, VendingMachine.A_CODE);
		assertEquals(donut, myVendingMachine.removeItem(VendingMachine.A_CODE));
 	}
	
	/**
	 * Test remove item from an empty slot.
	 * Postcondition: Test run will be true true if the exception is correctly caught, 
	 * when the slot mentioned is empty while removing an item.
	 * @throws VendingMachineException If trying to remove item which is not present in the code.
	 */
	@Test 
	public void testRemoveItem_EmptySlot() throws Exception {
		//assertEquals(donut, myVendingMachine.removeItem(VendingMachine.A_CODE));
		try{
			 myVendingMachine.removeItem(VendingMachine.D_CODE);			
	    }catch(VendingMachineException e){
	    	String msg="Slot D is empty -- cannot remove item";
	    	assertEquals(msg, e.getMessage());
	    }
 	}


	/**
	 * Test remove item for invalid code slot.
	 * Postcondition: Test run will be true if the exception is correctly caught when
	 * trying to remove item from an invalid slot.
	 * @throws VendingMachineException If trying to remove item from an invalid slot.
	 */
    @Test
    public void testRemoveItem_InvalidCode() throws Exception{
    	try{
    		myVendingMachine.removeItem("X");
        }catch(VendingMachineException e){
        	String msg="Invalid code for vending machine item";
        	assertEquals(msg, e.getMessage());
        }
	            
	}
    
	/**
	 * Test remove item of the slot, and check if trying to remove an item which is not belonged to that slot.
	 * Postcondition: Test run will be true if the exception is correctly caught, 
	 * when trying to remove the item from a different slot.
	 * @throws VendingMachineException If trying to purchase a removed item.
	 */
	@Test
	public void testRemoveItem_VerifyItem1() throws Exception {
		myVendingMachine.addItem(donut,"A");
		try{
			myVendingMachine.removeItem(VendingMachine.B_CODE);	
	    }catch(VendingMachineException e){
	    	String msg="Slot B is empty -- cannot remove item";
	    	assertEquals(msg, e.getMessage());
	    }
		
		
 	}
	
	/**
	 * Test remove item of the slot, and check if there is any item existing.
	 * Postcondition: Test run should return true if the exception is correctly caught,
	 * when trying to purchase the item which is removed.
	 * @throws VendingMachineException If trying to purchase a removed item.
	 */
	@Test 
	public void testRemoveItem_VerifyItem2() throws Exception {
		myVendingMachine.addItem(coke,"A");
		myVendingMachine.insertMoney(3.00);
		myVendingMachine.makePurchase(VendingMachine.A_CODE);
		try{
			myVendingMachine.removeItem(VendingMachine.A_CODE);
		}
		catch(VendingMachineException e){
			String msg =  "Slot A " + "is empty -- cannot remove item";
			assertEquals(msg, e.getMessage());
		}
 	}
	
	/**
	 * Test insert money into the machine, and check the balance.
	 * Postcondition: Test run will be true if the expected balance matches the actual balance.
	 */
	@Test
	public void testInsertMoney_CheckBalance() throws Exception{
		myVendingMachine.insertMoney(1.00);
		myVendingMachine.insertMoney(0.25);
		assertEquals(1.25, myVendingMachine.getBalance(), 0.0000001);
	}

	/**
	 * Test insert money of the slot for amount < 0
	 * Postcondition: An exception will be thrown for negative amount.
	 * @throws VendingMachineException If amount <0
	 */
	@Test
	public void testInsertMoney_NegativeAmount() throws Exception {
		
		try{
			myVendingMachine.insertMoney(-0.50);
				
	    }catch(VendingMachineException e){
	    	String msg="Invalid amount.  Amount must be >= 0";
	    	assertEquals(msg, e.getMessage());
	    }
		
				
	}
	

	/**
	 * Test insert money of the slot for amount = 0
	 * Postcondition: Test run will be true if exception is occurred for negative amount while inserting money 
	 *  
	 * @throws VendingMachineException If amount <0
	 */
	@Test 
	public void testInsertMoney_ZeroAmount() throws Exception{
		myVendingMachine.insertMoney(0.00);
	}
	
	/**
	 * Test insert money of the slot for amount > 0 and check balance
	 * Postcondition: Test case will fail if the inserted money doesn't match the balance.s
	 */
	@Test 
	public void testInsertMoney_WrongBalance() {
		myVendingMachine.insertMoney(1.00);
		myVendingMachine.insertMoney(0.25);
		assertEquals(1.75, myVendingMachine.getBalance(), 0.0000001);
	}
	
	
	/**
	 * Test get balance.
	 */
	@Test
	public void testGetBalance() {
		assertEquals(0.00, myVendingMachine.getBalance(), 0.000001);
	}
	
	
	/**
	 * Test get balance with change.
	 */
	@Test
	public void testGetBalance_withChange() {
		assertEquals(0.00, myVendingMachine.getBalance(), 0.000001);
		myVendingMachine.insertMoney(3.20);
		assertEquals(3.20, myVendingMachine.getBalance(), 0.000001);
	}

	

	/**
	 * Test make purchase for enough money and purchase item.
	 * Post Condition: Test case will run if the balance and the money in the Vending machine matches.
	 */
	@Test
	public void testMakePurchase_Good() {
		myVendingMachine.addItem(coke,"A");
		myVendingMachine.insertMoney(2.00);
		myVendingMachine.makePurchase("A");
		
		assertEquals(1.25, myVendingMachine.getBalance(), 0.000001);
	}


	/**
	 * Test make purchase for NOT enough money and purchase item.
	 * Post Condition: Test case will fail if the balance is zero.
	 */
	@Test
	public void testMakePurchase_ZeroBalance() {
		myVendingMachine.addItem(coke,"A");		
		assertFalse(myVendingMachine.makePurchase("A"));
	}

	/**
	 * Test make purchase for NOT enough money and purchase item.
	 * Post Condition: Test case will fail if the balance is not sufficient.
	 */
	@Test
	public void testMakePurchase_NotEnoughBalance() {
		myVendingMachine.addItem(coke,"A");
		myVendingMachine.insertMoney(0.50);
		
		assertFalse(myVendingMachine.makePurchase("A"));
	}
	
	/**
	 * Test make purchase for an empty slot.
	 * Post Condition: Test case will fail if there is no item in the specified slot.
	 */
	@Test
	public void testMakePurchase_EmptySlot() {
		
		myVendingMachine.insertMoney(0.50);
		assertFalse(myVendingMachine.makePurchase("A"));
	}
	
	/**
	 * Test return change.
	 */
	@Test
	public void testReturnChange_good() {
		myVendingMachine.insertMoney(1.20);
		assertEquals(1.20, myVendingMachine.returnChange(), 0.00001);
		assertEquals(0.00, myVendingMachine.getBalance(), 0.00001);
	}

	/**
	 * Test return change for Zero Balance.
	 */
	@Test
	public void testReturnChange_ZeroBalance() {
		assertEquals(0.00, myVendingMachine.returnChange(), 0.00001);
		assertEquals(0.00, myVendingMachine.getBalance(), 0.00001);
	}
	
}
